/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年5月23日
 */
package com.massyframework.beanskin.util.classloading;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import com.massyframework.beanskin.ModuleIdentifier;
import com.massyframework.beanskin.service.secure.SecureCapacity;
import com.massyframework.beanskin.util.CompoundEnumeration;
import com.massyframework.beanskin.util.version.Version;

/**
 * 模块的类加载器
 * @author huangkh
 */
public class ModuleClassLoader extends SelfFirstClassLoader
	implements ModuleIdentifier, LocalResourcesPathProvider, SecureCapacity{
	
	private ModuleIdentifier idenrifier;
	private List<LocalResourcesPath> resourcePaths;
	private LocalResourcesPath localResourcePath =
			new LocalResourcesPathImpl();
	
	private final String publicKey;
	private final String certificateName;
	private final String certificateData;
	private final Set<String> decryptClassNames;
	private final Set<String> decrptyScriptFileNames;
	private final boolean hotDeployment; 

	/**
	 * 使用{@code builder}创建新实例
	 * @param builder {@link Builder}
	 */
	protected ModuleClassLoader(Builder builder) {
		super(builder);
		this.idenrifier = Objects.requireNonNull(builder.idenrifier, "\"identifier\" cannot be null.");
		this.resourcePaths = builder.resourcePaths == null ?
				Collections.emptyList() :
					Collections.unmodifiableList(builder.resourcePaths);
		this.publicKey = builder.publicKey;
		this.certificateName = builder.certificateName;
		this.certificateData = builder.certificateData;
		this.decryptClassNames = builder.decryptClassNames == null ?
				Collections.emptySet() :
					Collections.unmodifiableSet(builder.decryptClassNames);
		this.decrptyScriptFileNames = builder.decryptScriptFileNames == null ?
				Collections.emptySet() :
					Collections.unmodifiableSet(builder.decryptScriptFileNames);
		this.hotDeployment = builder.hotDeploment;
	}
	

	@Override
	public String getGroupId() {
		return this.idenrifier.getGroupId();
	}

	@Override
	public String getModuleId() {
		return this.idenrifier.getModuleId();
	}

	@Override
	public Version getVersion() {
		return this.idenrifier.getVersion();
	}
	
	@Override
	public LocalResourcesPath getLocalResourcePath() {
		return this.localResourcePath;
	}
	
	@Override
	public boolean isDecryptClass(String className) {
		return this.decryptClassNames.contains(className);
	}
	
	@Override
	public boolean isDecryptScriptFile(String fileName) {
		return this.decrptyScriptFileNames.contains(fileName);
	}


	@Override
	public String getPublicKey() {
		return this.publicKey;
	}

	@Override
	public Set<String> getDecryptClassNames() {
		return this.decryptClassNames;
	}
	
	@Override
	public Set<String> getDescryptScriptFileNames() {
		return this.decrptyScriptFileNames;
	}

	@Override
	public String getX509Name() {
		return this.certificateName;
	}

	@Override
	public String getX509Encrypted() {
		return this.certificateData;
	}
	
	/**
	 * 在类加载器范围内加载{@code name}的资源，并排除{@code filter}过滤的搜索范围
	 * @param name {@link String},资源名称
	 * @param filter {@link ResourceLocateFilter}, 资源定位过滤器
	 * @return {@link Enumeration}
	 */
	public Enumeration<URL> getResources(String name, ResourceLocateFilter filter) throws IOException{
		return this.findResources(name, filter);
	}
	
	/**
	 * 是否要求支持热部署
	 * <p>
	 * 仅适用于jdk8, jdk8类加载器中使用getResource打开的资源，无法关闭，会导致热部署失败。
	 * 提供这个方法，用于对Fragment加载的URL进行打补丁, 使其能进行热部署。
	 * @return {@link boolean}
	 */
	protected boolean requireHotDeployment() {
		return this.hotDeployment;
	}

	/**
	 * 所依赖的本地资源路径
	 * @return {@link LocalResourcesPath}
	 */
	public List<LocalResourcesPath> getRequireLocalResourcePaths(){
		return this.resourcePaths;
	}

	@Override
	protected Class<?> customizeFindClass(String name) throws ClassNotFoundException {
		Class<?> result = null;

        //从LocalResourcesPath中查找
        for (LocalResourcesPath resourcesPath: this.resourcePaths) {
            try {
                result = resourcesPath.findClass(name, this.getParentClassLoader());
                if (result != null) {
                    return result;
                }
            }catch(ClassNotFoundException e) {

            }
        }

        return result;
	}

	@Override
	protected URL customizeFindResource(String name,  ResourceLocateFilter filter) {
		 //从LocalResourcesPath中查找资源
        for (LocalResourcesPath resourcesPath: this.resourcePaths) {
        	if (this.isExcludePaths(resourcesPath, filter)) {
	            continue;
        	}
        	
        	URL result = resourcesPath.findResource(name, this.getParentClassLoader());
            if (result != null) {
                return result;
            }
        }
        return null;
	}
	
	@Override
	protected Enumeration<URL> findResourcesWithUcp(String name) throws IOException {
		return super.findResourcesWithUcp(name);
	}

	@SuppressWarnings({"unchecked" })
	@Override
	protected Enumeration<URL> customizeFindResources(String name, ResourceLocateFilter filter) throws IOException {
		List<Enumeration<URL>> ems = new ArrayList<>();
		
        //从LocalResourcesPath中查找资源
        for (int i=0; i<this.resourcePaths.size(); i++) {
        	LocalResourcesPath resourcesPath = this.resourcePaths.get(i);
        	if (this.isExcludePaths(resourcesPath, filter)) {
        		continue;
        	}
        	
        	ems.add(resourcesPath.findResources(name, this.getParentClassLoader()));
        }

        return new CompoundEnumeration<URL>(ems.toArray(new Enumeration[ems.size()]));
	}
	
	@Override
	public int compareTo(ModuleIdentifier o) {
		int result = this.getGroupId().compareTo(o.getGroupId());
		if (result == 0) {
			result = this.getModuleId().compareTo(o.getModuleId());
			if (result == 0) {
				return this.getVersion().compareTo(o.getVersion());
			}
		}
		return result;
	}
	
	/**
	 * 判断{@link identifier}是否相同
	 * @param identifier {@link ModuleIdentifier}
	 * @return {@link boolean},返回{@code true}表示相同，否则返回{@code false}
	 */
	public boolean isEquals(ModuleIdentifier identifier) {
		return this.idenrifier.equals(identifier);
	}
		
	@Override
	public int hashCode() {
		return this.idenrifier.hashCode();
	}


	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ModuleClassLoader other = (ModuleClassLoader) obj;
		if (idenrifier == null) {
			if (other.idenrifier != null)
				return false;
		} else if (!idenrifier.equals(other.idenrifier))
			return false;
		return true;
	}



	@Override
	public String toString() {
		return "ModuleClassLoader [" + idenrifier.toIdentifier() + "]";
	}

	private class LocalResourcesPathImpl implements LocalResourcesPath {
		
		@Override
		public int compareTo(ModuleIdentifier o) {
			return idenrifier.compareTo(o);
		}

		@Override
		public String getGroupId() {
			return idenrifier.getGroupId();
		}

		@Override
		public String getModuleId() {
			return idenrifier.getModuleId();
		}

		@Override
		public Version getVersion() {
			return idenrifier.getVersion();
		}

		@Override
		public Class<?> findClass(String className, ClassLoader excludeLoader) throws ClassNotFoundException {
			synchronized (getClassLoadingLock(className)) {
                // First, check if the class has already been loaded
                Class<?> result = findLoadedClass(className);
                if (result == null) {
                    try {
                        result = findClassWithUcp(className);
                    }catch(ClassNotFoundException e){

                    }

                    if (result == null) {
                        boolean isNotNull = getParentClassLoader() != null;
                        if (isNotNull) {
                            if (getParentClassLoader() != excludeLoader) {
                                try {
                                    result = getParentClassLoader().loadClass(className);
                                }catch(ClassNotFoundException e) {

                                }
                            }
                        }
                    }
                }

                return result;
            }
		}

		@Override
		public URL findResource(String name, ClassLoader excludeLoader) {
			return  findResourceWithUcp(name);
		}

		@Override
		public Enumeration<URL> findResources(String name, ClassLoader excludeLoader) throws IOException {
            return findResourcesWithUcp(name);
		}

		@Override
		public String toString() {
			return "LocalResourcesPath[" + toIdentifier() + "]";
		}
		
	}


	/**
	 * 创建构建器实例
	 * @return {@link Builder}
	 */
	public static Builder newBuilder() {
		return new Builder();
	}
	
	/**
	 * 构建器
	 * @author huangkh
	 *
	 */
	public static class Builder extends SelfFirstClassLoader.Builder 
		implements  ModuleClassLoaderBuilder{
		
		private ModuleIdentifier idenrifier;
		private List<LocalResourcesPath> resourcePaths;
		private String publicKey;
		private String certificateName;
		private String certificateData;
		private Set<String> decryptClassNames;
		private Set<String> decryptScriptFileNames;
		private boolean hotDeploment = false;

		@Override
		public Builder certificate(String name, String data) {
			this.certificateName = name;
			this.certificateData = data;
			return this;
		}

		@Override
		public Builder decryptClassNames(Set<String> value) {
			this.decryptClassNames = value;
			return this;
		}
		
		

		@Override
		public Builder decryptScriptFileNames(Set<String> value) {
			this.decryptScriptFileNames = value;
			return this;
		}

		@Override
		public Builder publicKey(String value) {
			this.publicKey = value;
			return this;
		}

		/**
		 * 添加本地资源路径
		 * @param value {@link LocalResourcesPath}
		 * @return {@link Builder}
		 */
		@Override
		public Builder addLocalResourcesPath(LocalResourcesPath value) {
			if (value != null) {
				if (this.resourcePaths == null) {
					this.resourcePaths = new ArrayList<>();
				}
				this.resourcePaths.add(value);
			}
			return this;
		}
		
		/**
		 * 添加本地资源路径集合
		 * @param value {@link Collection}
		 * @return {@link Builder}
		 */
		@Override
		public Builder addLocalResourcesPaths(Collection<LocalResourcesPath> value) {
			if (value != null) {
				if (this.resourcePaths == null) {
					this.resourcePaths = new ArrayList<>();
				}
				this.resourcePaths.addAll(value);
			}
			return this;
		}
		
		
		@Override
		public Builder addURL(URL url) {
			super.addURL(url);
			return this;
		}

		@Override
		public Builder addURLs(Collection<URL> urls) {
			super.addURLs(urls);
			return this;
		}
		
		public Builder hotDeployment(boolean value) {
			this.hotDeploment = value;
			return this;
		}
		
		
		@Override
		public Builder resourceLocateFilterRepository(ResourceLocateFilterRepository value) {
			super.resourceLocateFilterRepository(value);
			return this;
		}

		@Override
		public Builder parentClassLoader(ClassLoader value) {
			super.parentClassLoader(value);
			return this;
		}
		
		/**
		 * 设置模块唯一标识符
		 * @param value {@link ModuleIdenfifier}
		 * @return {@link Builder}
		 */
		public Builder moduleIdentifier(ModuleIdentifier value) {
			this.idenrifier = value;
			return this;
		}
		

		@Override
		public ModuleClassLoader build() {
			return new ModuleClassLoader(this);
		}	
	}
}
